﻿using FreeDream.Common;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace FreeDream.Infrastructure
{
    public class JwtTokenHelper
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public JwtTokenHelper(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        #region JwtToken 方法一

        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims"></param>
        /// <param name="jwtSettings"></param>
        /// <returns></returns>
        public static string GenerateJwtToken(List<Claim> claims)
        {
            var symmetricKeyAsBase64 = AppSettings.App(new string[] { "Audience", "Secret" });
            var keyByteArray = Encoding.UTF8.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var issuer = AppSettings.App(new string[] { "Audience", "Issuer" });
            var audience = AppSettings.App(new string[] { "Audience", "Audience" });
            var expiresTime = int.Parse(AppSettings.App(new string[] { "Audience", "ExpiresTime" }));

            var authTime = DateTime.Now;
            var expiresAt = authTime.AddMinutes(expiresTime);
            var tokenHandler = new JwtSecurityTokenHandler();

            claims.Add(new Claim("Audience", audience));
            claims.Add(new Claim("Issuer", issuer));

            #region WriteToken

            //var tokenDescriptor = new SecurityTokenDescriptor
            //{
            //    Subject = new ClaimsIdentity(claims),
            //    Issuer = issuer,
            //    Audience = audience,
            //    IssuedAt = authTime,//token生成时间
            //    Expires = expiresAt,
            //    //NotBefore = authTime,
            //    TokenType = JwtBearerDefaults.AuthenticationScheme,//"Bearer",
            //    //对称秘钥，签名证书
            //    SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature)
            //};
            //var token = tokenHandler.CreateToken(tokenDescriptor);
            //return tokenHandler.WriteToken(token);

            #endregion

            var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, audience: audience, claims: claims, expires: expiresAt, notBefore: DateTime.Now
                , signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature));
            return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

        }

        /// <summary>
        /// 验证Token
        /// </summary>
        /// <returns></returns>
        public static TokenValidationParameters ValidParameters()
        {
            var symmetricKeyAsBase64 = AppSettings.App(new string[] { "Audience", "Secret" });
            var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var issuer = AppSettings.App(new string[] { "Audience", "Issuer" });
            var audience = AppSettings.App(new string[] { "Audience", "Audience" });

            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);

            // 令牌验证参数
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = issuer,//发行人
                ValidateAudience = true,
                ValidAudience = audience,//订阅人
                ValidateLifetime = true,    //是否验证Token有效期，使用当前时间与Token的Claims中的NotBefore和Expires对比
                ClockSkew = TimeSpan.FromSeconds(30),   //ClockSkew 属性，默认是5分钟缓冲过期时间
                //RequireExpirationTime = true, //过期时间
            };

            return tokenValidationParameters;
        }

        /// <summary>
        /// 从令牌中获取数据声明
        /// </summary>
        /// <param name="token">令牌</param>
        /// <returns></returns>
        public static IEnumerable<Claim> ParseToken(string token)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var validateParameter = ValidParameters();
            if (token.Contains("Bearer"))
            {
                token = token.Replace("Bearer ", "");
            }

            try
            {
                tokenHandler.ValidateToken(token, validateParameter, out SecurityToken validatedToken);

                var jwtToken = tokenHandler.ReadJwtToken(token);
                return jwtToken.Claims;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                // return null if validation fails
                return null;
            }
        }

        /// <summary>
        /// jwt token校验
        /// </summary>
        /// <param name="jwtToken"></param>
        /// <returns></returns>
        public static LoginUser ValidateJwtToken(IEnumerable<Claim> jwtToken)
        {
            try
            {
                var userData = jwtToken.FirstOrDefault(x => x.Type == ClaimTypes.UserData)?.Value;
                var loginUser = System.Text.Json.JsonSerializer.Deserialize<LoginUser>(userData); //JsonConvert.DeserializeObject<LoginUser>(userData);
                //var permissions = CacheService.GetUserPerms(GlobalConstant.UserPermKEY + loginUser?.UserId);
                //if (loginUser?.UserName == "admin")
                //{
                //    permissions = new List<string>() { GlobalConstant.AdminPerm };
                //}
                //if (permissions == null) return null;
                //loginUser.Permissions = permissions;
                return loginUser;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        /// <summary>
        ///组装Claims
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public static List<Claim> AddClaims(LoginUser user)
        {
            //if (user?.Permissions.Count > 50)
            //{
            //    user.Permissions = new List<string>();
            //}
            var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.PrimarySid, user.UserId.ToString()),
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(ClaimTypes.UserData, System.Text.Json.JsonSerializer.Serialize(user))
                };

            return claims;
        }

        ///// <summary>
        ///// 获取用户身份信息
        ///// </summary>
        ///// <param name="httpContext"></param>
        ///// <returns></returns>
        //public static LoginUser GetLoginUser(HttpContext httpContext)
        //{
        //    string token = httpContext.GetToken();

        //    if (!string.IsNullOrEmpty(token))
        //    {
        //        return ValidateJwtToken(ParseToken(token));
        //    }
        //    return null;
        //}

        #endregion

        #region JwtToken 方法二

        /// <summary>
        /// 生成JwtToken
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJwt(TokenModelJwt tokenModel)
        {
            string iss = AppSettings.App(new string[] { "Audience", "Issuer" });
            string aud = AppSettings.App(new string[] { "Audience", "Audience" });
            string secret = AppSecretConfig.Audience_Secret_String;

            /*
            * 特别重要：
            1、这里将用户的部分信息，比如 uid 存到了Claim 中，如果你想知道如何在其他地方将这个 uid从 Token 中取出来，请看下边的SerializeJwt() 方法，或者在整个解决方案，搜索这个方法，看哪里使用了！
            2、你也可以研究下 HttpContext.User.Claims ，具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。
            */
            var claims = new List<Claim> {
                new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                //这个就是过期时间，目前是过期1000秒，可自定义，注意JWT有自己的缓冲过期时间
                new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
                new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(1000).ToString()),
                new Claim(JwtRegisteredClaimNames.Iss,iss),
                new Claim(JwtRegisteredClaimNames.Aud,aud),
                
                //new Claim(ClaimTypes.Role,tokenModel.Role),//为了解决一个用户多个角色(比如：Admin,System)，用下边的方法
            };
            // 可以将一个用户的多个角色全部赋予；
            claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

            //秘钥 (SymmetricSecurityKey 对安全性的要求，密钥的长度太短会报出异常)
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(
                issuer: iss,
                claims: claims,
                signingCredentials: creds);

            var jwtHandler = new JwtSecurityTokenHandler();
            var encodedJwt = jwtHandler.WriteToken(jwt);

            return encodedJwt;
        }

        /// <summary>
        /// 解析JwtToken
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static TokenModelJwt SerializeJwt(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            TokenModelJwt tokenModelJwt = new TokenModelJwt();
            // token校验
            if (jwtStr.IsNotEmptyOrNull() && jwtHandler.CanReadToken(jwtStr))
            {

                JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);

                //object role;

                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out object role);

                tokenModelJwt = new TokenModelJwt
                {
                    Uid = (jwtToken.Id).ObjToInt(),
                    Role = role != null ? role.ObjToString() : "",
                };
            }
            return tokenModelJwt;
        }

        #endregion

    }

    public class TokenModelJwt
    {
        /// <summary>
        /// Id
        /// </summary>
        public long Uid { get; set; }
        /// <summary>
        /// 角色
        /// </summary>
        public string Role { get; set; }
        /// <summary>
        /// 职能
        /// </summary>
        public string Work { get; set; }

    }
}
